﻿#region COPYRIGHT© 2009-2012 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using FlitBit.Core;
using FlitBit.Core.Parallel;

namespace FlitBit.Cache
{
	/// <summary>
	///   Local memory cache implementation of ICache
	/// </summary>
	/// <typeparam name="TKey">key type K</typeparam>
	/// <typeparam name="TValue">item type V</typeparam>
	public sealed class LocalMemoryCache<TKey, TValue> : Disposable, ICache<TKey, TValue>
		where TValue : class
	{
		readonly ConcurrentDictionary<TKey, TValue> _cache = new ConcurrentDictionary<TKey, TValue>();

		/// <summary>
		/// Creates new instance.
		/// </summary>
		/// <param name="name"></param>
		public LocalMemoryCache(string name)
		{
			Name = name;
		}

		/// <summary>
		///   Performs dispose logic.
		/// </summary>
		/// <param name="disposing">whether called from Dispose method</param>
		/// <returns>true if disposed as a result of this call</returns>
		protected override bool PerformDispose(bool disposing)
		{
			return true;
		}

		#region ICache<K,V> Members

		/// <summary>
		///   Gets the cache's name.
		/// </summary>
		public string Name { get; private set; }

		/// <summary>
		///   Asynchronously gets an item from the cache.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Get(TKey key, Continuation<Tuple<TKey, TValue>> continuation)
		{
			TValue item;
			continuation(null, this._cache.TryGetValue(key, out item) ? Tuple.Create(key, item) : null);
		}

		/// <summary>
		///   Asynchronously gets items from the cache.
		/// </summary>
		/// <param name="keys">keys of items to retrieve.</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Get(IEnumerable<TKey> keys, Continuation<IEnumerable<Tuple<TKey, TValue>>> continuation)
		{
			Go.Parallel(() =>
			{
				var accum = new ConcurrentBag<Tuple<TKey, TValue>>();
				try
				{
					Parallel.ForEach(keys, k =>
					{
						TValue it;
						if (_cache.TryGetValue(k, out it))
						{
							accum.Add(new Tuple<TKey, TValue>(k, it));
						}
					});
				}
				catch (Exception e)
				{
					continuation(e, null);
					return;
				}
				continuation(null, accum);
			});
		}

		/// <summary>
		///   Asynchronously puts an item in the cache.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="item">the item.</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Put(TKey key, TValue item, Continuation<Tuple<TKey, TValue>> continuation)
		{
			try
			{
				_cache.AddOrUpdate(key, item, (k, v) => item);
			}
			catch (Exception e)
			{
				if (continuation != null)
				{
					continuation(e, null);
				}
				return;
			}
			if (continuation != null)
			{
				continuation(null, Tuple.Create(key, item));
			}
		}

		/// <summary>
		///   Asynchronously puts items in the cache.
		/// </summary>
		/// <param name="items">items to put into the cache.</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Put(IEnumerable<Tuple<TKey, TValue>> items, Continuation<IEnumerable<Tuple<TKey, TValue>>> continuation)
		{
			Go.Parallel(() =>
			{
				var accum = new ConcurrentBag<Tuple<TKey, TValue>>();
				try
				{
					Parallel.ForEach(items, it =>
					{
						_cache.AddOrUpdate(it.Item1, it.Item2, (k, v) => it.Item2);
						accum.Add(it);
					});
				}
				catch (Exception e)
				{
					if (continuation != null)
					{
						continuation(e, null);
					}
					return;
				}
				if (continuation != null)
				{
					continuation(null, accum);
				}
			});
		}

		/// <summary>
		///   Asynchronously removes an item from the cache.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Remove(TKey key, Continuation<Tuple<TKey, TValue>> continuation)
		{
			var result = default(Tuple<TKey, TValue>);
			try
			{
				TValue item;
				if (_cache.TryRemove(key, out item))
				{
					result = Tuple.Create(key, item);
				}
			}
			catch (Exception e)
			{
				if (continuation != null)
				{
					continuation(e, null);
				}
				return;
			}
			if (continuation != null)
			{
				continuation(null, result);
			}
		}

		/// <summary>
		///   Asynchronously removes items from the cache.
		/// </summary>
		/// <param name="keys">keys of items to retrieve.</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Remove(IEnumerable<TKey> keys, Continuation<IEnumerable<Tuple<TKey, TValue>>> continuation)
		{
			Go.Parallel(() =>
			{
				var res = new ConcurrentBag<Tuple<TKey, TValue>>();
				try
				{
					Parallel.ForEach(keys, k =>
					{
						TValue it;
						if (_cache.TryRemove(k, out it))
						{
							res.Add(Tuple.Create(k, it));
						}
					});
				}
				catch (Exception e)
				{
					if (continuation != null)
					{
						continuation(e, null);
					}
					return;
				}
				if (continuation != null)
				{
					continuation(null, res);
				}
			});
		}

		/// <summary>
		///   Flushes all items from the cache.
		/// </summary>
		public void FlushAll()
		{
			_cache.Clear();
		}

		#endregion
	}
}